package com.shyp.daodikeji;

import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.*;
import java.util.*;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x9.X962Parameters;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.asn1.x9.X9ECPoint;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.jcajce.spec.SM2ParameterSpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.FixedPointCombMultiplier;
import org.bouncycastle.math.ec.custom.gm.SM2P256V1Curve;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;

import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.util.Strings;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.bouncycastle.util.io.pem.PemWriter;

import javax.crypto.Cipher;

//SM2 基础库
public class Sm2Lib
{
    //SM2算法默认用户ID，目前开放平台不会使用非默认用户ID
    public static String DEFAULT_USER_ID = "1234567812345678";
    public static String DEFAULT_CHARSET = "utf8";

    public static final String SM2_ECC_GY_VAL = "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0";
    public static final String SM2_ECC_GX_VAL = "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7";
    public static final SM2P256V1Curve CURVE = new SM2P256V1Curve();
    public final static BigInteger SM2_ECC_N = CURVE.getOrder();
    public final static BigInteger SM2_ECC_H = CURVE.getCofactor();
    public final static BigInteger SM2_ECC_GX = new BigInteger(SM2_ECC_GX_VAL, 16);
    public final static BigInteger SM2_ECC_GY = new BigInteger(SM2_ECC_GY_VAL, 16);
    public static final ECPoint G_POINT = CURVE.createPoint(SM2_ECC_GX, SM2_ECC_GY);
    public static final ECDomainParameters DOMAIN_PARAMS = new ECDomainParameters(CURVE, G_POINT, SM2_ECC_N, SM2_ECC_H);

    public final static BigInteger SM2_ECC_P = CURVE.getQ();
    public final static BigInteger SM2_ECC_A = CURVE.getA().toBigInteger();
    public final static BigInteger SM2_ECC_B = CURVE.getB().toBigInteger();

    //public static final int CURVE_LEN = BCECUtils.getCurveLength(DOMAIN_PARAMS);

    public static final EllipticCurve JDK_CURVE = new EllipticCurve(new ECFieldFp(SM2_ECC_P), SM2_ECC_A, SM2_ECC_B);
    public static final java.security.spec.ECPoint JDK_G_POINT = new java.security.spec.ECPoint(
            G_POINT.getAffineXCoord().toBigInteger(), G_POINT.getAffineYCoord().toBigInteger());
    public static final java.security.spec.ECParameterSpec JDK_EC_SPEC = new java.security.spec.ECParameterSpec(
            JDK_CURVE, JDK_G_POINT, SM2_ECC_N, SM2_ECC_H.intValue());

    private static BouncyCastleProvider provider;

    static {
        provider = new BouncyCastleProvider();
        Security.addProvider(provider);
    }

    public static String decrypt(String cipherTextBase64, String privateKey) throws Exception {
        return decrypt(cipherTextBase64, privateKey, DEFAULT_CHARSET);
    }

    public static String decrypt(String cipherTextBase64, String privateKey, String charset) throws Exception {
        byte[] cipher = Base64.getDecoder().decode(cipherTextBase64);
        boolean isMatch = Pattern.matches("^[0-9a-f]+$", privateKey.toLowerCase());
        if (isMatch) {
            ECPrivateKeyParameters ecPrivateKeyParameters = BCECUtils.createECPrivateKeyParameters(privateKey, DOMAIN_PARAMS);
            byte[] buf = decrypt(ecPrivateKeyParameters, cipher);
            //将解密后的明文按指定字符集编码后返回
            try {
                String strContent = new String(buf, charset);
                return strContent;
            } catch (UnsupportedEncodingException e) {
                throw new Exception(e);
            }
        } else {
            byte[] privateKeyByte = Base64.getDecoder().decode(privateKey);
            // 解析X509格式SM2私钥
            PrivateKey sm2PrivateKey = parsePKCS8PrivateKey(privateKeyByte);
            // 使用SM2私钥解密
            byte[] buf = sm2Decrypt(cipher, sm2PrivateKey);
            //将解密后的明文按指定字符集编码后返回
            try {
                String strContent = new String(buf, charset);
                return strContent;
            } catch (UnsupportedEncodingException e) {
                throw new Exception(e);
            }
        }
    }

    public static String encrypt(String plainText, String publicKey) throws Exception {
        return encrypt(plainText, publicKey, DEFAULT_CHARSET);
    }

    public static String encrypt(String plainText, String publicKey, String charset) throws Exception {
        byte[] plain = plainText.getBytes(charset);
        boolean isMatch = Pattern.matches("^[0-9a-f]+$", publicKey.toLowerCase());
        if (isMatch) {
            ECPoint point = CURVE.decodePoint(ByteUtils.fromHexString(publicKey));
            ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(point, DOMAIN_PARAMS);
            byte[] cipher = encrypt(ecPublicKeyParameters, plain);
            //将密文Base64编码后返回
            String strContent = Base64.getEncoder().encodeToString(cipher);
            return strContent;
        } else {
            // 解析PKCS8格式SM2私钥
            byte[] publicKeyByte = Base64.getDecoder().decode(publicKey);
            PublicKey sm2PublicKey = parseX509PublicKey(publicKeyByte);
            byte[] cipher = sm2Encrypt(plain, sm2PublicKey);
            //将密文Base64编码后返回
            String strContent = Base64.getEncoder().encodeToString(cipher);
            return strContent;
        }
    }

    public static String sign(String content, String privateKey) throws Exception {
        return sign(content, privateKey, null, DEFAULT_CHARSET);
    }

    public static String sign(String content, String privateKey, String userId, String charset) throws Exception {
        if (StringUtils.isEmpty(userId)) {
            userId = DEFAULT_USER_ID;
        }
        byte[] message = content.getBytes(charset);
        boolean isMatch = Pattern.matches("^[0-9a-f]+$", privateKey.toLowerCase());
        if (isMatch) {
            ECPrivateKeyParameters ecPrivateKeyParameters = BCECUtils.createECPrivateKeyParameters(privateKey, DOMAIN_PARAMS);
            byte[] signature = sign(ecPrivateKeyParameters, Strings.toByteArray(userId), message);
            try {
                String sign = Base64.getEncoder().encodeToString(signature);
                return sign;
            } catch (Exception e) {
                throw new Exception(e);
            }
        } else {
            byte[] privateKeyByte = Base64.getDecoder().decode(privateKey);
            PrivateKey sm2PrivateKey = parsePKCS8PrivateKey(privateKeyByte);
            byte[] signature = sm2Sign(message, sm2PrivateKey, userId);
            String sign = Base64.getEncoder().encodeToString(signature);
            return sign;
        }
    }

    public static boolean verify(String content, String publicKey, String sign) throws Exception {
        return verify(content, publicKey, sign, null, DEFAULT_CHARSET);
    }

    public static boolean verify(String content, String publicKey, String sign, String userId, String charset) throws Exception {
        if (StringUtils.isEmpty(userId)) {
            userId = DEFAULT_USER_ID;
        }

        byte[] message = content.getBytes(charset);
        byte[] signature = Base64.getDecoder().decode(sign);
        boolean isMatch = Pattern.matches("^[0-9a-f]+$", publicKey.toLowerCase());
        if (isMatch) {
            ECPoint point = CURVE.decodePoint(ByteUtils.fromHexString(publicKey));
            ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(point, DOMAIN_PARAMS);
            boolean valid = verify(ecPublicKeyParameters, Strings.toByteArray(userId), message, signature);
            return valid;
        } else {
            byte[] publicKeyByte = Base64.getDecoder().decode(publicKey);
            PublicKey sm2PublicKey = parseX509PublicKey(publicKeyByte);
            boolean valid = sm2Verify(signature, message, sm2PublicKey, userId);
            return valid;
        }
    }

    private static byte[] sm2Encrypt(byte[] plain, PublicKey sm2PublicKey) throws Exception {
        try {
            Cipher sm2CipherEngine = Cipher.getInstance("SM2", "BC");
            sm2CipherEngine.init(Cipher.ENCRYPT_MODE, sm2PublicKey);
            return sm2CipherEngine.doFinal(plain);
        } catch (Exception e) {
            throw new Exception(e);
        }
    }

    private static byte[] sm2Decrypt(byte[] cipher, PrivateKey sm2PrivateKey) throws Exception {
        try {
            Cipher sm2CipherEngine = Cipher.getInstance("SM2", "BC");
            sm2CipherEngine.init(Cipher.DECRYPT_MODE, sm2PrivateKey);
            return sm2CipherEngine.doFinal(cipher);
        } catch (Exception e) {
            throw new Exception(e);
        }
    }

    private static byte[] sm2Sign(byte[] message, PrivateKey sm2PrivateKey, String sm2UserId) throws Exception {
        try {
            Signature sm2SignEngine = Signature.getInstance("SM3withSM2");
            sm2SignEngine.setParameter(new SM2ParameterSpec(
                    Strings.toByteArray(sm2UserId)));
            sm2SignEngine.initSign(sm2PrivateKey);
            sm2SignEngine.update(message);
            return sm2SignEngine.sign();
        } catch (Exception e) {
            throw new Exception(e);
        }
    }

    private static boolean sm2Verify(byte[] signature, byte[] message, PublicKey publicKey, String sm2UserId) {
        try {
            Signature sm2SignEngine = Signature.getInstance("SM3withSM2");
            sm2SignEngine.setParameter(new SM2ParameterSpec(Strings.toByteArray(sm2UserId)));
            sm2SignEngine.initVerify(publicKey);
            sm2SignEngine.update(message);
            return sm2SignEngine.verify(signature);
        } catch (Exception e) {
            return false;
        }
    }

    private static PublicKey parseX509PublicKey(byte[] x509PublicKey) throws Exception {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance("EC");
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(x509PublicKey);
            return keyFactory.generatePublic(keySpec);
        } catch (Exception e) {
            throw new Exception(e);
        }
    }

    private static PrivateKey parsePKCS8PrivateKey(byte[] pkcs8PriateKey) throws Exception {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance("EC");
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8PriateKey);
            return keyFactory.generatePrivate(keySpec);
        } catch (Exception e) {
            throw new Exception(e);
        }
    }

    public static String getSignContent(Map<String, String> sortedParams) {
        StringBuffer content = new StringBuffer();
        List<String> keys = new ArrayList<>(sortedParams.keySet());
        Collections.sort(keys);
        int index = 0;
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = sortedParams.get(key);
            if (areNotEmpty(key, value)) {
                content.append((index == 0 ? "" : "&") + key + "=" + value);
                index++;
            }
        }
        return content.toString();
    }

    private static boolean areNotEmpty(String... values) {
        boolean result = true;
        if (values == null || values.length == 0) {
            result = false;
        } else {
            for (String value : values) {
                result &= !isEmpty(value);
            }
        }
        return result;
    }

    private static boolean isEmpty(String value) {
        int strLen;
        if (value == null || (strLen = value.length()) == 0) {
            return true;
        }
        for (int i = 0; i < strLen; i++) {
            if ((Character.isWhitespace(value.charAt(i)) == false)) {
                return false;
            }
        }
        return true;
    }

    /**
     * @param pubKeyParams 公钥
     * @param srcData      原文
     * @return 默认输出C1C3C2顺序的密文。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @throws InvalidCipherTextException
     */
    private static byte[] encrypt(final ECPublicKeyParameters pubKeyParams, final byte[] srcData) throws InvalidCipherTextException {
        return encrypt(SM2Engine.Mode.C1C3C2, pubKeyParams, srcData);
    }

    /**
     * @param priKeyParams 私钥
     * @param sm2Cipher    默认输入C1C3C2顺序的密文。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @return 原文。SM2解密返回了数据则一定是原文，因为SM2自带校验，如果密文被篡改或者密钥对不上，都是会直接报异常的。
     * @throws InvalidCipherTextException
     */
    private static byte[] decrypt(final ECPrivateKeyParameters priKeyParams, final byte[] sm2Cipher)
            throws InvalidCipherTextException {
        return decrypt(SM2Engine.Mode.C1C3C2, priKeyParams, sm2Cipher);
    }

    /**
     * @param mode         指定密文结构，新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
     * @param pubKeyParams 公钥
     * @param srcData      原文
     * @return 根据mode不同，输出的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @throws InvalidCipherTextException
     */
    private static byte[] encrypt(final SM2Engine.Mode mode, final ECPublicKeyParameters pubKeyParams, final byte[] srcData)
            throws InvalidCipherTextException {
        final SM2Engine engine = new SM2Engine(mode);
        final ParametersWithRandom pwr = new ParametersWithRandom(pubKeyParams, new SecureRandom());
        engine.init(true, pwr);
        return engine.processBlock(srcData, 0, srcData.length);
    }

    /**
     * @param mode         指定密文结构，新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
     * @param priKeyParams 私钥
     * @param sm2Cipher    根据mode不同，需要输入的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @return 原文。SM2解密返回了数据则一定是原文，因为SM2自带校验，如果密文被篡改或者密钥对不上，都是会直接报异常的。
     * @throws InvalidCipherTextException
     */
    private static byte[] decrypt(final SM2Engine.Mode mode, final ECPrivateKeyParameters priKeyParams, final byte[] sm2Cipher)
            throws InvalidCipherTextException {
        final SM2Engine engine = new SM2Engine(mode);
        engine.init(false, priKeyParams);
        return engine.processBlock(sm2Cipher, 0, sm2Cipher.length);
    }

    /**
     * 签名
     *
     * @param priKeyParams 私钥
     * @param withId       可以为null，若为null，则默认withId为字节数组:"1234567812345678".getBytes()
     * @param srcData      源数据
     * @return DER编码后的签名值
     * @throws CryptoException
     */
    private static byte[] sign(final ECPrivateKeyParameters priKeyParams, final byte[] withId, final byte[] srcData)
            throws CryptoException {
        SM2Signer signer = new SM2Signer();
        CipherParameters param = null;
        ParametersWithRandom pwr = new ParametersWithRandom(priKeyParams, new SecureRandom());
        if (withId != null) {
            param = new ParametersWithID(pwr, withId);
        } else {
            param = pwr;
        }
        signer.init(true, param);
        signer.update(srcData, 0, srcData.length);
        return signer.generateSignature();
    }

    /**
     * 验签
     *
     * @param pubKeyParams 公钥
     * @param withId       可以为null，若为null，则默认withId为字节数组:"1234567812345678".getBytes()
     * @param srcData      原文
     * @param sign         DER编码的签名值
     * @return 验签成功返回true，失败返回false
     */
    private static boolean verify(final ECPublicKeyParameters pubKeyParams, final byte[] withId, final byte[] srcData,
                                  final byte[] sign) {
        final SM2Signer signer = new SM2Signer();
        CipherParameters param;
        if (withId != null) {
            param = new ParametersWithID(pubKeyParams, withId);
        } else {
            param = pubKeyParams;
        }
        signer.init(false, param);
        signer.update(srcData, 0, srcData.length);
        return signer.verifySignature(sign);
    }

    /**
     * 生成秘钥对(工具函数)
     * @return 0 公钥， 1私钥
     */
    public static String[] genKey()
    {
        AsymmetricCipherKeyPair kPair = BCECUtils.generateKeyPairParameter(DOMAIN_PARAMS, new SecureRandom());
        ECPrivateKeyParameters ecPriv = (ECPrivateKeyParameters)kPair.getPrivate();
        ECPublicKeyParameters ecPub = (ECPublicKeyParameters)kPair.getPublic();
        BigInteger privateKey = ecPriv.getD();
        ECPoint publicKey = ecPub.getQ();

        byte[] priv = privateKey.toByteArray();
        byte[] pub = publicKey.getEncoded(false);
        if (priv.length == 33)
        {
            byte[] newPriv = new byte[32];
            System.arraycopy(priv, 1, newPriv, 0, 32);
            priv = newPriv;
        }
        String[] keyPairs = new String[2];
        keyPairs[0] = ByteUtils.toHexString(pub);
        keyPairs[1] = ByteUtils.toHexString(priv);

        return keyPairs;
    }
}


/**
 * 这个工具类的方法，也适用于其他基于BC库的ECC算法
 */
class BCECUtils {
    private static final String ALGO_NAME_EC = "EC";
    private static final String PEM_STRING_PUBLIC = "PUBLIC KEY";
    private static final String PEM_STRING_ECPRIVATEKEY = "EC PRIVATE KEY";

    /**
     * 生成ECC密钥对
     *
     * @return ECC密钥对
     */
    public static AsymmetricCipherKeyPair generateKeyPairParameter(
            ECDomainParameters domainParameters, SecureRandom random) {
        ECKeyGenerationParameters keyGenerationParams = new ECKeyGenerationParameters(domainParameters,
                random);
        ECKeyPairGenerator keyGen = new ECKeyPairGenerator();
        keyGen.init(keyGenerationParams);
        return keyGen.generateKeyPair();
    }


    public static KeyPair generateKeyPair(ECDomainParameters domainParameters, SecureRandom random)
            throws NoSuchProviderException, NoSuchAlgorithmException,
            InvalidAlgorithmParameterException {
        KeyPairGenerator kpg = KeyPairGenerator.getInstance(ALGO_NAME_EC, BouncyCastleProvider.PROVIDER_NAME);
        ECParameterSpec parameterSpec = new ECParameterSpec(domainParameters.getCurve(), domainParameters.getG(),
                domainParameters.getN(), domainParameters.getH());
        kpg.initialize(parameterSpec, random);
        return kpg.generateKeyPair();
    }

    public static int getCurveLength(ECKeyParameters ecKey) {
        return getCurveLength(ecKey.getParameters());
    }

    public static int getCurveLength(ECDomainParameters domainParams) {
        return (domainParams.getCurve().getFieldSize() + 7) / 8;
    }

    public static byte[] fixToCurveLengthBytes(int curveLength, byte[] src) {
        if (src.length == curveLength) {
            return src;
        }

        byte[] result = new byte[curveLength];
        if (src.length > curveLength) {
            System.arraycopy(src, src.length - result.length, result, 0, result.length);
        } else {
            System.arraycopy(src, 0, result, result.length - src.length, src.length);
        }
        return result;
    }

    /**
     * @param dHex             十六进制字符串形式的私钥d值，如果是SM2算法，Hex字符串长度应该是64（即32字节）
     * @param domainParameters EC Domain参数，一般是固定的，如果是SM2算法的可参考{@link Sm2Lib#DOMAIN_PARAMS}
     * @return
     */
    public static ECPrivateKeyParameters createECPrivateKeyParameters(
            String dHex, ECDomainParameters domainParameters) {
        return createECPrivateKeyParameters(ByteUtils.fromHexString(dHex), domainParameters);
    }

    /**
     * @param dBytes           字节数组形式的私钥d值，如果是SM2算法，应该是32字节
     * @param domainParameters EC Domain参数，一般是固定的，如果是SM2算法的可参考{@link Sm2Lib#DOMAIN_PARAMS}
     * @return
     */
    public static ECPrivateKeyParameters createECPrivateKeyParameters(
            byte[] dBytes, ECDomainParameters domainParameters) {
        return createECPrivateKeyParameters(new BigInteger(1, dBytes), domainParameters);
    }

    /**
     * @param d                大数形式的私钥d值
     * @param domainParameters EC Domain参数，一般是固定的，如果是SM2算法的可参考{@link Sm2Lib#DOMAIN_PARAMS}
     * @return
     */
    public static ECPrivateKeyParameters createECPrivateKeyParameters(
            BigInteger d, ECDomainParameters domainParameters) {
        return new ECPrivateKeyParameters(d, domainParameters);
    }

    /**
     * 根据EC私钥构造EC公钥
     *
     * @param priKey ECC私钥参数对象
     * @return
     */
    public static ECPublicKeyParameters buildECPublicKeyByPrivateKey(ECPrivateKeyParameters priKey) {
        ECDomainParameters domainParameters = priKey.getParameters();
        ECPoint q = new FixedPointCombMultiplier().multiply(domainParameters.getG(), priKey.getD());
        return new ECPublicKeyParameters(q, domainParameters);
    }

    /**
     * @param x                大数形式的公钥x分量
     * @param y                大数形式的公钥y分量
     * @param curve            EC曲线参数，一般是固定的，如果是SM2算法的可参考{@link Sm2Lib#CURVE}
     * @param domainParameters EC Domain参数，一般是固定的，如果是SM2算法的可参考{@link Sm2Lib#DOMAIN_PARAMS}
     * @return
     */
    public static ECPublicKeyParameters createECPublicKeyParameters(
            BigInteger x, BigInteger y, ECCurve curve, ECDomainParameters domainParameters) {
        return createECPublicKeyParameters(x.toByteArray(), y.toByteArray(), curve, domainParameters);
    }

    /**
     * @param xHex             十六进制形式的公钥x分量，如果是SM2算法，Hex字符串长度应该是64（即32字节）
     * @param yHex             十六进制形式的公钥y分量，如果是SM2算法，Hex字符串长度应该是64（即32字节）
     * @param curve            EC曲线参数，一般是固定的，如果是SM2算法的可参考{@link Sm2Lib#CURVE}
     * @param domainParameters EC Domain参数，一般是固定的，如果是SM2算法的可参考{@link Sm2Lib#DOMAIN_PARAMS}
     * @return
     */
    public static ECPublicKeyParameters createECPublicKeyParameters(
            String xHex, String yHex, ECCurve curve, ECDomainParameters domainParameters) {
        return createECPublicKeyParameters(ByteUtils.fromHexString(xHex), ByteUtils.fromHexString(yHex),
                curve, domainParameters);
    }

    /**
     * @param xBytes           十六进制形式的公钥x分量，如果是SM2算法，应该是32字节
     * @param yBytes           十六进制形式的公钥y分量，如果是SM2算法，应该是32字节
     * @param curve            EC曲线参数，一般是固定的，如果是SM2算法的可参考{@link Sm2Lib#CURVE}
     * @param domainParameters EC Domain参数，一般是固定的，如果是SM2算法的可参考{@link Sm2Lib#DOMAIN_PARAMS}
     * @return
     */
    public static ECPublicKeyParameters createECPublicKeyParameters(
            byte[] xBytes, byte[] yBytes, ECCurve curve, ECDomainParameters domainParameters) {
        final byte uncompressedFlag = 0x04;
        int curveLength = getCurveLength(domainParameters);
        xBytes = fixToCurveLengthBytes(curveLength, xBytes);
        yBytes = fixToCurveLengthBytes(curveLength, yBytes);
        byte[] encodedPubKey = new byte[1 + xBytes.length + yBytes.length];
        encodedPubKey[0] = uncompressedFlag;
        System.arraycopy(xBytes, 0, encodedPubKey, 1, xBytes.length);
        System.arraycopy(yBytes, 0, encodedPubKey, 1 + xBytes.length, yBytes.length);
        return new ECPublicKeyParameters(curve.decodePoint(encodedPubKey), domainParameters);
    }

    public static ECPrivateKeyParameters convertPrivateKeyToParameters(BCECPrivateKey ecPriKey) {
        ECParameterSpec parameterSpec = ecPriKey.getParameters();
        ECDomainParameters domainParameters = new ECDomainParameters(parameterSpec.getCurve(), parameterSpec.getG(),
                parameterSpec.getN(), parameterSpec.getH());
        return new ECPrivateKeyParameters(ecPriKey.getD(), domainParameters);
    }

    public static ECPublicKeyParameters convertPublicKeyToParameters(BCECPublicKey ecPubKey) {
        ECParameterSpec parameterSpec = ecPubKey.getParameters();
        ECDomainParameters domainParameters = new ECDomainParameters(parameterSpec.getCurve(), parameterSpec.getG(),
                parameterSpec.getN(), parameterSpec.getH());
        return new ECPublicKeyParameters(ecPubKey.getQ(), domainParameters);
    }

    public static BCECPublicKey createPublicKeyFromSubjectPublicKeyInfo(SubjectPublicKeyInfo subPubInfo)
            throws NoSuchProviderException,
            NoSuchAlgorithmException, InvalidKeySpecException, IOException {
        return BCECUtils.convertX509ToECPublicKey(subPubInfo.toASN1Primitive().getEncoded(ASN1Encoding.DER));
    }

    /**
     * 将ECC私钥转换为PKCS8标准的字节流
     *
     * @param priKey
     * @param pubKey 可以为空，但是如果为空的话得到的结果OpenSSL可能解析不了
     * @return
     */
    public static byte[] convertECPrivateKeyToPKCS8(
            ECPrivateKeyParameters priKey, ECPublicKeyParameters pubKey) {
        ECDomainParameters domainParams = priKey.getParameters();
        ECParameterSpec spec = new ECParameterSpec(domainParams.getCurve(), domainParams.getG(),
                domainParams.getN(), domainParams.getH());
        BCECPublicKey publicKey = null;
        if (pubKey != null) {
            publicKey = new BCECPublicKey(ALGO_NAME_EC, pubKey, spec,
                    BouncyCastleProvider.CONFIGURATION);
        }
        BCECPrivateKey privateKey = new BCECPrivateKey(ALGO_NAME_EC, priKey, publicKey,
                spec, BouncyCastleProvider.CONFIGURATION);
        return privateKey.getEncoded();
    }

    /**
     * 将PKCS8标准的私钥字节流转换为私钥对象
     *
     * @param pkcs8Key
     * @return
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws InvalidKeySpecException
     */
    public static BCECPrivateKey convertPKCS8ToECPrivateKey(byte[] pkcs8Key)
            throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
        PKCS8EncodedKeySpec peks = new PKCS8EncodedKeySpec(pkcs8Key);
        KeyFactory kf = KeyFactory.getInstance(ALGO_NAME_EC, BouncyCastleProvider.PROVIDER_NAME);
        return (BCECPrivateKey) kf.generatePrivate(peks);
    }

    /**
     * 将PKCS8标准的私钥字节流转换为PEM
     *
     * @param encodedKey
     * @return
     * @throws IOException
     */
    public static String convertECPrivateKeyPKCS8ToPEM(byte[] encodedKey) throws IOException {
        return convertEncodedDataToPEM(PEM_STRING_ECPRIVATEKEY, encodedKey);
    }

    /**
     * 将PEM格式的私钥转换为PKCS8标准字节流
     *
     * @param pemString
     * @return
     * @throws IOException
     */
    public static byte[] convertECPrivateKeyPEMToPKCS8(String pemString) throws IOException {
        return convertPEMToEncodedData(pemString);
    }

    /**
     * 将ECC私钥转换为SEC1标准的字节流
     * openssl d2i_ECPrivateKey函数要求的DER编码的私钥也是SEC1标准的，
     * 这个工具函数的主要目的就是为了能生成一个openssl可以直接“识别”的ECC私钥.
     * 相对RSA私钥的PKCS1标准，ECC私钥的标准为SEC1
     *
     * @param priKey
     * @param pubKey
     * @return
     * @throws IOException
     */
    public static byte[] convertECPrivateKeyToSEC1(
            ECPrivateKeyParameters priKey, ECPublicKeyParameters pubKey) throws IOException {
        byte[] pkcs8Bytes = convertECPrivateKeyToPKCS8(priKey, pubKey);
        PrivateKeyInfo pki = PrivateKeyInfo.getInstance(pkcs8Bytes);
        ASN1Encodable encodable = pki.parsePrivateKey();
        ASN1Primitive primitive = encodable.toASN1Primitive();
        byte[] sec1Bytes = primitive.getEncoded();
        return sec1Bytes;
    }

    /**
     * 将SEC1标准的私钥字节流恢复为PKCS8标准的字节流
     *
     * @param sec1Key
     * @return
     * @throws IOException
     */
    public static byte[] convertECPrivateKeySEC1ToPKCS8(byte[] sec1Key) throws IOException {
        /**
         * 参考org.bouncycastle.asn1.pkcs.PrivateKeyInfo和
         * org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey，逆向拼装
         */
        X962Parameters params = getDomainParametersFromName(Sm2Lib.JDK_EC_SPEC, false);
        ASN1OctetString privKey = new DEROctetString(sec1Key);
        ASN1EncodableVector v = new ASN1EncodableVector();
        v.add(new ASN1Integer(0)); //版本号
        v.add(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params)); //算法标识
        v.add(privKey);
        DERSequence ds = new DERSequence(v);
        return ds.getEncoded(ASN1Encoding.DER);
    }

    /**
     * 将SEC1标准的私钥字节流转为BCECPrivateKey对象
     *
     * @param sec1Key
     * @return
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws InvalidKeySpecException
     * @throws IOException
     */
    public static BCECPrivateKey convertSEC1ToBCECPrivateKey(byte[] sec1Key)
            throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, IOException {
        PKCS8EncodedKeySpec peks = new PKCS8EncodedKeySpec(convertECPrivateKeySEC1ToPKCS8(sec1Key));
        KeyFactory kf = KeyFactory.getInstance(ALGO_NAME_EC, BouncyCastleProvider.PROVIDER_NAME);
        return (BCECPrivateKey) kf.generatePrivate(peks);
    }

    /**
     * 将SEC1标准的私钥字节流转为ECPrivateKeyParameters对象
     * openssl i2d_ECPrivateKey函数生成的DER编码的ecc私钥是：SEC1标准的、带有EC_GROUP、带有公钥的，
     * 这个工具函数的主要目的就是为了使Java程序能够“识别”openssl生成的ECC私钥
     *
     * @param sec1Key
     * @return
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws InvalidKeySpecException
     */
    public static ECPrivateKeyParameters convertSEC1ToECPrivateKey(byte[] sec1Key)
            throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, IOException {
        BCECPrivateKey privateKey = convertSEC1ToBCECPrivateKey(sec1Key);
        return convertPrivateKeyToParameters(privateKey);
    }

    /**
     * 将ECC公钥对象转换为X509标准的字节流
     *
     * @param pubKey
     * @return
     */
    public static byte[] convertECPublicKeyToX509(ECPublicKeyParameters pubKey) {
        ECDomainParameters domainParams = pubKey.getParameters();
        ECParameterSpec spec = new ECParameterSpec(domainParams.getCurve(), domainParams.getG(),
                domainParams.getN(), domainParams.getH());
        BCECPublicKey publicKey = new BCECPublicKey(ALGO_NAME_EC, pubKey, spec,
                BouncyCastleProvider.CONFIGURATION);
        return publicKey.getEncoded();
    }

    /**
     * 将X509标准的公钥字节流转为公钥对象
     *
     * @param x509Bytes
     * @return
     * @throws NoSuchProviderException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    public static BCECPublicKey convertX509ToECPublicKey(byte[] x509Bytes) throws NoSuchProviderException,
            NoSuchAlgorithmException, InvalidKeySpecException {
        X509EncodedKeySpec eks = new X509EncodedKeySpec(x509Bytes);
        KeyFactory kf = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
        return (BCECPublicKey) kf.generatePublic(eks);
    }

    /**
     * 将X509标准的公钥字节流转为PEM
     *
     * @param encodedKey
     * @return
     * @throws IOException
     */
    public static String convertECPublicKeyX509ToPEM(byte[] encodedKey) throws IOException {
        return convertEncodedDataToPEM(PEM_STRING_PUBLIC, encodedKey);
    }

    /**
     * 将PEM格式的公钥转为X509标准的字节流
     *
     * @param pemString
     * @return
     * @throws IOException
     */
    public static byte[] convertECPublicKeyPEMToX509(String pemString) throws IOException {
        return convertPEMToEncodedData(pemString);
    }

    /**
     * copy from BC
     *
     * @param genSpec
     * @return
     */
    public static X9ECParameters getDomainParametersFromGenSpec(ECGenParameterSpec genSpec) {
        return getDomainParametersFromName(genSpec.getName());
    }

    /**
     * copy from BC
     *
     * @param curveName
     * @return
     */
    public static X9ECParameters getDomainParametersFromName(String curveName) {
        X9ECParameters domainParameters;
        try {
            if (curveName.charAt(0) >= '0' && curveName.charAt(0) <= '2') {
                ASN1ObjectIdentifier oidID = new ASN1ObjectIdentifier(curveName);
                domainParameters = ECUtil.getNamedCurveByOid(oidID);
            } else {
                if (curveName.indexOf(' ') > 0) {
                    curveName = curveName.substring(curveName.indexOf(' ') + 1);
                    domainParameters = ECUtil.getNamedCurveByName(curveName);
                } else {
                    domainParameters = ECUtil.getNamedCurveByName(curveName);
                }
            }
        } catch (IllegalArgumentException ex) {
            domainParameters = ECUtil.getNamedCurveByName(curveName);
        }
        return domainParameters;
    }

    /**
     * copy from BC
     *
     * @param ecSpec
     * @param withCompression
     * @return
     */
    public static X962Parameters getDomainParametersFromName(
            java.security.spec.ECParameterSpec ecSpec, boolean withCompression) {
        X962Parameters params;

        if (ecSpec instanceof ECNamedCurveSpec) {
            ASN1ObjectIdentifier curveOid = ECUtil.getNamedCurveOid(((ECNamedCurveSpec) ecSpec).getName());
            if (curveOid == null) {
                curveOid = new ASN1ObjectIdentifier(((ECNamedCurveSpec) ecSpec).getName());
            }
            params = new X962Parameters(curveOid);
        } else if (ecSpec == null) {
            params = new X962Parameters(DERNull.INSTANCE);
        } else {
            ECCurve curve = EC5Util.convertCurve(ecSpec.getCurve());

            X9ECParameters ecP = new X9ECParameters(
                    curve,
                    new X9ECPoint(EC5Util.convertPoint(curve, ecSpec.getGenerator()), withCompression),
                    ecSpec.getOrder(),
                    BigInteger.valueOf(ecSpec.getCofactor()),
                    ecSpec.getCurve().getSeed());

            //// 如果是1.62或更低版本的bcprov-jdk15on应该使用以下这段代码，因为高版本的EC5Util.convertPoint没有向下兼容
            /*
            X9ECParameters ecP = new X9ECParameters(
                curve,
                EC5Util.convertPoint(curve, ecSpec.getGenerator(), withCompression),
                ecSpec.getOrder(),
                BigInteger.valueOf(ecSpec.getCofactor()),
                ecSpec.getCurve().getSeed());
            */

            params = new X962Parameters(ecP);
        }

        return params;
    }

    private static String convertEncodedDataToPEM(String type, byte[] encodedData) throws IOException {
        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
        PemWriter pWrt = new PemWriter(new OutputStreamWriter(bOut));
        try {
            PemObject pemObj = new PemObject(type, encodedData);
            pWrt.writeObject(pemObj);
        } finally {
            pWrt.close();
        }
        return new String(bOut.toByteArray());
    }

    private static byte[] convertPEMToEncodedData(String pemString) throws IOException {
        ByteArrayInputStream bIn = new ByteArrayInputStream(pemString.getBytes());
        PemReader pRdr = new PemReader(new InputStreamReader(bIn));
        try {
            PemObject pemObject = pRdr.readPemObject();
            return pemObject.getContent();
        } finally {
            pRdr.close();
        }
    }
}


